#ifdef COMPILE_OPENGL

#include "CBL_Debug.h"
#include "COGL_VertexBufferAtomic.h"
#include <OpenGL/OpenGL.h>
#include <OpenGL/glext.h>
#include "COGL_Extensions.h"

CB_SUBNAMESPACE_START(OpenGL)

// -----------------------------------------------------------------------

static GLenum s_NativeStorageType[RenderLib::CVertexBufferDescriptor::NumStorageTypes] =
{
	GL_STREAM_DRAW,
    GL_STATIC_DRAW,
    GL_DYNAMIC_DRAW,
};

// -----------------------------------------------------------------------

#define ENUM_ITEM(_Name, _Size, _Normlized, _Elements, _OpenGLFormat, _D3D9Format, _D3D10Format) _OpenGLFormat,
static GLenum s_NativeVertexFormat[RenderLib::CVertexBufferDescriptor::NumVertexFormats] =
{
	0,
	#include "CRL_VertexFormatsEnum.h"
};
#undef ENUM_ITEM

// -----------------------------------------------------------------------

#ifdef GL_VERSION_3_0
static GLbitfield s_NativeLockFlushType[RenderLib::IBufferAtomic::NumLockTypes] =
{
    GL_MAP_INVALIDATE_BUFFER_BIT,
    GL_MAP_INVALIDATE_RANGE_BIT,
    GL_MAP_READ,
    GL_MAP_READ | GL_MAP_INVALIDATE_RANGE_BIT,
};
#endif

// -----------------------------------------------------------------------

static GLenum s_NativeLockAccessType[RenderLib::IBufferAtomic::NumLockTypes] =
{
	GL_WRITE_ONLY,
    GL_WRITE_ONLY,
    GL_READ_ONLY,
    GL_READ_WRITE
};

// -----------------------------------------------------------------------

CVertexBufferAtomic::CVertexBufferAtomic(const RenderLib::CVertexBufferDescriptor& _rDescriptor)
	: m_BufferResourceId(0)
	, m_Descriptor(_rDescriptor)
{
	glGenBuffers(1, &m_BufferResourceId);
    glBufferData(GL_ARRAY_BUFFER, m_Descriptor.GetSizeByte(), NULL, s_NativeStorageType[m_Descriptor.GetStorageType()]);
}

// -----------------------------------------------------------------------

CVertexBufferAtomic::~CVertexBufferAtomic()
{
    glDeleteBuffers(1, &m_BufferResourceId);
}

// -----------------------------------------------------------------------

void* CVertexBufferAtomic::Lock(RenderLib::IBufferAtomic::ELockType _LockType)
{
	glBindBuffer(GL_ARRAY_BUFFER, m_BufferResourceId);
    
    if (_LockType == RenderLib::IBufferAtomic::LockOverwrite)
    {
    	glBufferData(GL_ARRAY_BUFFER, m_Descriptor.GetSizeByte(), NULL, s_NativeStorageType[m_Descriptor.GetStorageType()]);
    }
    
    return glMapBuffer(GL_ARRAY_BUFFER, s_NativeLockAccessType[_LockType]);    
}

// -----------------------------------------------------------------------

void* CVertexBufferAtomic::Lock(RenderLib::IBufferAtomic::ELockType _LockType, const RenderLib::CRangeDescriptor& _rRange)
{
	Debug::Assert(CB_DEBUG_MSG(_rRange.GetOffset() != RenderLib::CRangeDescriptor::InvalidValue,
                               "To partially lock a vertexbuffer provide Offset"));
    Debug::Assert(CB_DEBUG_MSG(_rRange.GetCount() != RenderLib::CRangeDescriptor::InvalidValue,
                               "To partially lock a vertexbuffer provide Count"));
    
    glBindBuffer(GL_ARRAY_BUFFER, m_BufferResourceId);
    
    if (_LockType == RenderLib::IBufferAtomic::LockOverwrite)
    {
    	glBufferSubData(GL_ARRAY_BUFFER, _rRange.GetOffset(), _rRange.GetCount(), NULL);	
    }
    
	#ifdef GL_VERSION_3_0
    return glMapBufferRange(GL_ARRAY_BUFFER, _rRange.GetOffset(), _rRange.GetCount(), s_NativeLockFlushType[_LockType]);
	#endif
    
    return (uint8*)glMapBuffer(GL_ARRAY_BUFFER, s_NativeLockAccessType[_LockType]) + _rRange.GetOffset();
}

// -----------------------------------------------------------------------

void CVertexBufferAtomic::Unlock()
{
    glUnmapBuffer(GL_ARRAY_BUFFER);
}

// -----------------------------------------------------------------------

void CVertexBufferAtomic::Update(const void *_pData)
{
	glBindBuffer(GL_ARRAY_BUFFER, m_BufferResourceId);
    glBufferData(GL_ARRAY_BUFFER, m_Descriptor.GetSizeByte(), _pData, s_NativeStorageType[m_Descriptor.GetStorageType()]);
}

// -----------------------------------------------------------------------

void CVertexBufferAtomic::Update(const void *_pData, const RenderLib::CRangeDescriptor& _rRange)
{
	Debug::Assert(CB_DEBUG_MSG(_rRange.GetOffset() != RenderLib::CRangeDescriptor::InvalidValue,
                               "To partially update a vertexbuffer provide Offset"));
    Debug::Assert(CB_DEBUG_MSG(_rRange.GetCount() != RenderLib::CRangeDescriptor::InvalidValue,
                               "To partially update a vertexbuffer provide Count"));
    
    glBindBuffer(GL_ARRAY_BUFFER, m_BufferResourceId);
    glBufferSubData(GL_ARRAY_BUFFER, _rRange.GetOffset(), _rRange.GetCount(), _pData);
}

// -----------------------------------------------------------------------

int CVertexBufferAtomic::Bind(uint8 _Stream, uint32 _Offset, uint32 CB_UNUSED(_Size))
{
	glBindBuffer(GL_ARRAY_BUFFER, m_BufferResourceId);
    
    uint32 StreamOffset = _Offset;
    for (uint32 SlotIdx=0; SlotIdx<m_Descriptor.GetNumSlots(); ++SlotIdx)
    {
        glVertexAttribPointer(	SlotIdx, m_Descriptor.GetSlotElementCount(SlotIdx), 
        						s_NativeVertexFormat[m_Descriptor.GetSlotFormat(SlotIdx)], 
                                m_Descriptor.GetSlotNormalized(SlotIdx), 
                                m_Descriptor.GetStride(), 
                                BUFFER_OFFSET(StreamOffset));
                                
    	StreamOffset += m_Descriptor.GetSlotSize(SlotIdx);
    }
    
    glEnableClientState(GL_VERTEX_ARRAY);
    
    return glGetError();
}

// -----------------------------------------------------------------------

int CVertexBufferAtomic::Unbind()
{
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glDisableClientState(GL_VERTEX_ARRAY);
    
    return glGetError();
}

CB_SUBNAMESPACE_END

#endif